/*
 * Copyright (c) 2005 Jakub Jermar
 * Copyright (c) 2011 Gedare Bloom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "mmu_support.h"
#include <rtems/score/cpu.h>
#include <rtems/score/heap.h>
#include "stdio.h"

extern Heap_Control *RTEMS_Malloc_Heap;

/* this should be in sparc64.h */
static void dump_istate( CPU_Interrupt_frame *istate ) {
  printk("TSTATE=%lx\n", istate->tstate);
  printk("TPC=%lx\n", istate->tpc);
  printk("TNPC=%lx\n", istate->tnpc);
  printk("TSP=%lx\n", istate->o6_sp);
}
  
void sparc64_fast_instruction_access_mmu_miss_handler(
    uint64_t vector_number,
    CPU_Interrupt_frame *istate
) {
  tlb_tag_access_reg_t tag;
  uintptr_t page_8k;

  /* get the miss tag */
  __asm__ __volatile__("mov %1, %%g1\n\t" \
                       "ldxa [%%g1] %2, %0\n\t"
      : "=r" (tag) : "n" (VA_IMMU_TAG_ACCESS), "n" (ASI_IMMU) : "g1");

  /* get the page */
  page_8k = (uint64_t) tag.vpn << MMU_PAGE_WIDTH;

  dump_istate(istate);

  /* TODO: determine whether to install the mapping or to fault */
  /* install a writable identity mapping */
  sparc64_itlb_insert(page_8k, p_to_f(page_8k), PAGESIZE_8K, 0, true, false);
}

void sparc64_fast_data_access_mmu_miss_handler(
    uint64_t vector_number,
    CPU_Interrupt_frame *istate
) {
  tlb_tag_access_reg_t tag;
  uintptr_t page_8k;

  /* get the miss tag */
  __asm__ __volatile__("mov %1, %%g1\n\t" \
                       "ldxa [%%g1] %2, %0\n\t"
      : "=r" (tag) : "n" (VA_DMMU_TAG_ACCESS), "n" (ASI_DMMU) : "g1");

  /* get the page */
  page_8k = (uint64_t) tag.vpn << MMU_PAGE_WIDTH;

  dump_istate(istate);

  /* TODO: determine whether to install the mapping or to fault */
  /* install a writable identity mapping */
  sparc64_dtlb_insert(page_8k, p_to_f(page_8k), PAGESIZE_8K, 
      0, true, true, false);
}

void sparc64_fast_data_access_protection_handler(
    uint64_t vector_number,
    CPU_Interrupt_frame *istate
) {
  // TODO: do something about violation...
  printk("data protection violation\n");

  dump_istate(istate);
  while(1);
}

void sparc64_dtlb_insert(uintptr_t page, uintptr_t frame, int pagesize, 
    uint16_t context, bool writable, bool cacheable, bool locked)
{
  tlb_tag_access_reg_t tag;
  tlb_data_t data;
  page_address_t pg;
  frame_address_t fr;
  
  pg.address = page;
  fr.address = frame;

  tag.context = context;
  tag.vpn = pg.vpn;
  
  dtlb_tag_access_write(tag.value);
  
  data.value = 0;
  data.v = true;
  data.size = pagesize;
  data.pfn = fr.pfn;
  data.l = locked;
  data.cp = cacheable;
  data.cv = cacheable;
  data.p = true;
  data.w = writable;
  data.g = false;
  dtlb_data_in_write(data.value);
}

void sparc64_itlb_insert(uintptr_t page, uintptr_t frame, int pagesize,
    uint16_t context, bool cacheable, bool locked)
{
  tlb_tag_access_reg_t tag;
  tlb_data_t data;
  page_address_t pg;
  frame_address_t fr;
  
  pg.address = page;
  fr.address = frame;

  tag.context = context;
  tag.vpn = pg.vpn;

  itlb_tag_access_write(tag.value);

  data.value = 0;
  data.v = true;
  data.size = pagesize;
  data.pfn = fr.pfn;
  data.l = locked;
  data.cp = cacheable;
  data.p = true;
  data.w = false;
  data.g = false;

  itlb_data_in_write(data.value);
}

void sparc64_tlb_invalidate_pages(uintptr_t first_page, size_t num_pages)
{
  int i;
//  printk("invalidate %lu pages starting at %X\n",num_pages, first_page);


  /* TODO: sanity check (currently assuming all 8k pages) */
  for ( i = 0; i < num_pages; i++ ) {
    itlb_demap(
        TLB_DEMAP_PAGE, 
        TLB_DEMAP_PRIMARY, 
        first_page + i * MMU_PAGE_SIZE
    );
    dtlb_demap(
        TLB_DEMAP_PAGE, 
        TLB_DEMAP_PRIMARY, 
        first_page + i * MMU_PAGE_SIZE
    );
  }
}

void sparc64_mmu_initialize(void)
{
  void *ignored;
  _CPU_ISR_install_vector(
      SPARC_SYNCHRONOUS_TRAP(SPARC64_TRAP_FAST_DATA_ACCESS_PROTECTION),
      sparc64_fast_data_access_protection_handler,
      &ignored
  );
  _CPU_ISR_install_vector(
      SPARC_SYNCHRONOUS_TRAP(SPARC64_TRAP_FAST_DATA_ACCESS_MMU_MISS),
      sparc64_fast_data_access_mmu_miss_handler,
      &ignored
  );
  _CPU_ISR_install_vector(
      SPARC_SYNCHRONOUS_TRAP(SPARC64_TRAP_FAST_INSTRUCTION_ACCESS_MMU_MISS),
      sparc64_fast_instruction_access_mmu_miss_handler,
      &ignored
  );

  /* TODO: clean out pages that were loaded by us during system initialization
   * in particular the heap; leave the workspace alone for now it is tricky
   * because of the _ISR_Vector_table
   */
/*  dtlb_demap(TLB_DEMAP_PAGE, TLB_DEMAP_PRIMARY, RTEMS_Malloc_Heap->area_begin);
  dtlb_demap(TLB_DEMAP_PAGE, TLB_DEMAP_PRIMARY, 
      RTEMS_Malloc_Heap->area_begin + 0x80000);
*/
  /* FIXME: for now leave the 4M page mapped over the workspace. */
#if 0
 dtlb_demap(TLB_DEMAP_PAGE, TLB_DEMAP_PRIMARY, 0x400000);
  /* now get the vector table remapped before taking any faults!
   * this table is biggest on sparc64 (8k)*/
  sparc64_dtlb_insert(
      _ISR_Vector_table,
      p_to_f(_ISR_Vector_table),
      PAGESIZE_8K,
      true,
      true,
      true
  );
#endif
  /* TODO: demap itlb too? */

}

void sparc64_tlb_print(void) {
//  tlb_print();
}

